#include "SQLUnionQuery.h"

#include "../../../Exception/UnsupportedOperationException.h"
#include "../../../utils/instanceof.h"
#include "../../../utils/ifeq.h"

#include "SQLUnionQueryTableSource.h"

#include "../../visitor/SQLASTVisitor.h"

SETCALSS(SQLUnionQuery);

SQLUnionOperator_ptr SQLUnionQuery::getOperator()
{
  return operator_;
}

void SQLUnionQuery::setOperator(SQLUnionOperator_ptr operator_)
{
  this->operator_ = operator_;
}

SQLUnionQuery::SQLUnionQuery()
{
  relations = std::make_shared<std::list<SQLSelectQuery_ptr>>();
  operator_ = SQLUnionOperator::UNION;
}

SQLUnionQuery::SQLUnionQuery(DbType_ptr dbType)
{
  relations = std::make_shared<std::list<SQLSelectQuery_ptr>>();
  this->dbType = dbType;
}

SQLUnionQuery::SQLUnionQuery(SQLSelectQuery_ptr left, SQLUnionOperator_ptr operator_, SQLSelectQuery_ptr right)
{
  relations = std::make_shared<std::list<SQLSelectQuery_ptr>>();
  this->setLeft(left);
  this->operator_ = operator_;
  this->setRight(right);
}

std::shared_ptr<std::list<SQLSelectQuery_ptr>> SQLUnionQuery::getRelations()
{
  return relations;
}

bool SQLUnionQuery::isEmpty()
{
  return relations->empty();
}

void SQLUnionQuery::addRelation(SQLSelectQuery_ptr relation)
{
  if (relation != NULL)
  {
    // relation->setParent(this);
  }

  relations->push_back(relation);
}

SQLSelectQuery_ptr SQLUnionQuery::getLeft()
{
  if (relations->empty())
  {
    return NULL;
  }
  std::list<SQLSelectQuery_ptr>::iterator it = relations->begin();
  std::advance(it, 0); // 迭代器前进i个元素，注意i是从0开始
  return (*it);
}

void SQLUnionQuery::setLeft(SQLSelectQuery_ptr left)
{
  // if (left != NULL) {
  //     left->setParent(this);
  // }

  if (relations->empty())
  {
    relations->push_back(left);
  }
  else if (relations->size() <= 2)
  {
    std::list<SQLSelectQuery_ptr>::iterator it = relations->begin();
    std::advance(it, 0); // 迭代器前进i个元素，注意i是从0开始
    relations->insert(it, left);
  }
  else
  {
    throw new UnsupportedOperationException(make_string_ptr("multi-union"));
  }
}

SQLSelectQuery_ptr SQLUnionQuery::getRight()
{
  if (relations->size() < 2)
  {
    return NULL;
  }
  if (relations->size() == 2)
  {
    std::list<SQLSelectQuery_ptr>::iterator it = relations->begin();
    std::advance(it, 1);
    return (*it);
  }

  throw new UnsupportedOperationException(make_string_ptr("multi-union"));
}

void SQLUnionQuery::setRight(SQLSelectQuery_ptr right)
{
  if (right != NULL)
  {
    // right->setParent(this);
  }

  if (relations->empty())
  {
    relations->push_back(NULL);
  }

  if (relations->size() == 1)
  {
    relations->push_back(right);
  }
  else if (relations->size() == 2)
  {
    std::list<SQLSelectQuery_ptr>::iterator it = relations->begin();
    std::advance(it, 1);
    relations->insert(it, right);
  }
  else
  {
    throw new UnsupportedOperationException(make_string_ptr("multi-union"));
  }
}

SQLOrderBy_ptr SQLUnionQuery::getOrderBy()
{
  return orderBy;
}

void SQLUnionQuery::setOrderBy(SQLOrderBy_ptr orderBy)
{
  if (orderBy != NULL)
  {
    orderBy->setParent(SharedObject(SQLUnionQuery));
  }
  this->orderBy = orderBy;
}

void SQLUnionQuery::accept0(SQLASTVisitor_ptr visitor)
{
  if (visitor->visit(SharedObject(SQLUnionQuery)))
  {
    for (SQLSelectQuery_ptr relation : *relations)
    {
      relation->accept(visitor);
    }

    if (orderBy != NULL)
    {
      orderBy->accept(visitor);
    }

    if (limit != NULL)
    {
      limit->accept(visitor);
    }
  }
  visitor->endVisit(SharedObject(SQLUnionQuery));
}

SQLLimit_ptr SQLUnionQuery::getLimit()
{
  return limit;
}

void SQLUnionQuery::setLimit(SQLLimit_ptr limit)
{
  if (limit != NULL)
  {
    limit->setParent(SharedObject(SQLUnionQuery));
  }
  this->limit = limit;
}

SQLObject_ptr SQLUnionQuery::clone()
{
  SQLObject_ptr clone_tmp = std::shared_ptr<SQLUnionQuery>(new SQLUnionQuery());
  std::shared_ptr<SQLUnionQuery> x = std::dynamic_pointer_cast<SQLUnionQuery>(clone_tmp);
  // SQLUnionQuery *x = new SQLUnionQuery();

  x->parenthesized = parenthesized;

  for (SQLSelectQuery_ptr relation : *relations)
  {
    SQLSelectQuery_ptr r = std::dynamic_pointer_cast<SQLSelectQuery>(relation->clone());
    // r->setParent(x);
    x->relations->push_back(r);
  }

  x->operator_ = operator_;

  if (orderBy != NULL)
  {
    x->setOrderBy(std::dynamic_pointer_cast<SQLOrderBy>(orderBy->clone()));
  }

  if (limit != NULL)
  {
    x->setLimit(std::dynamic_pointer_cast<SQLLimit>(limit->clone()));
  }

  x->dbType = dbType;

  return x;
}

SQLSelectQueryBlock_ptr SQLUnionQuery::getFirstQueryBlock()
{
  SQLSelectQuery_ptr left = getLeft();

  if (instanceof <SQLSelectQuery, SQLSelectQueryBlock>(left))
  {
    return std::dynamic_pointer_cast<SQLSelectQueryBlock>(left);
  }

  if (instanceof <SQLSelectQuery, SQLUnionQuery>(left))
  {
    return (std::dynamic_pointer_cast<SQLUnionQuery>(left))->getFirstQueryBlock();
  }

  return NULL;
}

DbType_ptr SQLUnionQuery::getDbType()
{
  return dbType;
}

void SQLUnionQuery::setDbType(DbType_ptr dbType)
{
  this->dbType = dbType;
}

BOOL_ptr SQLUnionQuery::replace(SQLSelectQuery_ptr cmp, SQLSelectQuery_ptr target)
{
  std::list<SQLSelectQuery_ptr>::iterator it = relations->begin();
  for (int i = 0; i < relations->size(); i++)
  {
    std::advance(it, i);
    SQLSelectQuery_ptr r = (*it);
    if (r == cmp)
    {
      // if (cmp != NULL) {
      //     cmp->setParent(this);
      // }
      relations->insert(it, cmp);
      return BOOL::TRUE;
    }
  }

  return BOOL::FALSE;
}

std::shared_ptr<std::list<SQLSelectQuery_ptr>> SQLUnionQuery::getChildren()
{
  bool bracket = this->parenthesized && !(instanceof <SQLObject, SQLUnionQueryTableSource>(parent));

  if (relations->size() > 2)
  {
    return relations;
  }

  SQLSelectQuery_ptr left = getLeft();
  SQLSelectQuery_ptr right = getRight();

  if ((!bracket) && 
  instanceof <SQLSelectQuery, SQLUnionQuery>(left) && 
  (std::dynamic_pointer_cast<SQLUnionQuery>(left))->getOperator() == operator_ && 
  !right->isParenthesized() && 
  orderBy == NULL)
  {
    std::shared_ptr<SQLUnionQuery> leftUnion = std::dynamic_pointer_cast<SQLUnionQuery>(left);

    std::shared_ptr<std::list<SQLSelectQuery_ptr>> rights = std::make_shared<std::list<SQLSelectQuery_ptr>>();
    rights->push_back(right);

    for (;;)
    {
      SQLSelectQuery_ptr leftLeft = leftUnion->getLeft();
      SQLSelectQuery_ptr leftRight = leftUnion->getRight();

      if ((!leftUnion->isParenthesized()) && 
      leftUnion->getOrderBy() == NULL && 
      (!leftLeft->isParenthesized()) && 
      (!leftRight->isParenthesized()) && 
      instanceof <SQLSelectQuery, SQLUnionQuery>(leftLeft) && 
      (std::dynamic_pointer_cast<SQLUnionQuery>(leftLeft))->getOperator() == operator_)
      {
        rights->push_back(leftRight);
        leftUnion = std::dynamic_pointer_cast<SQLUnionQuery>(leftLeft);
        continue;
      }
      else
      {
        rights->push_back(leftRight);
        rights->push_back(leftLeft);
      }
      break;
    }
    rights->reverse();

    return rights;
  }

  std::shared_ptr<std::list<SQLSelectQuery_ptr>> res = std::make_shared<std::list<SQLSelectQuery_ptr>>();
  res->push_back(left);
  res->push_back(right);
  return res;
}

bool SQLUnionQuery::equals(Object_ptr o)
{
  if (this == o.get())
  {
    return true;
  }
  if (o == NULL || getClass() != o->getClass())
  {
    return false;
  }

  SQLUnionQuery *that = dynamic_cast<SQLUnionQuery *>(o.get());

  if (parenthesized != that->parenthesized)
  {
    return false;
  }
  if (!relations->empty() ? !IFEQ::ifeq(relations, that->relations) : !that->relations->empty())
  {
    return false;
  }
  if (operator_ != that->operator_)
  {
    return false;
  }
  if (orderBy != NULL ? !orderBy->equals(that->orderBy) : that->orderBy != NULL)
  {
    return false;
  }
  if (limit != NULL ? !limit->equals(that->limit) : that->limit != NULL)
  {
    return false;
  }
  return dbType == that->dbType;
}

int SQLUnionQuery::hashCode()
{
  int result = (parenthesized ? 1 : 0);
  // result = 31 * result + (!relations.empty()? relations->hashCode() : 0);
  // result = 31 * result + (operator_ != NULL ? operator_->hashCode() : 0);
  result = 31 * result + (orderBy != NULL ? orderBy->hashCode() : 0);
  result = 31 * result + (limit != NULL ? limit->hashCode() : 0);
  // result = 31 * result + (dbType != NULL ? dbType->hashCode() : 0);
  return result;
}
